package cn.iocoder.yudao.framework.security.core.filter;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Token 过滤器，验证 token 的有效性
 * 验证通过后，获得 {@link LoginUser} 信息，并加入到 Spring Security 上下文
 *
 * @author 芋道源码
 */
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final SecurityProperties securityProperties;

    private final GlobalExceptionHandler globalExceptionHandler;

    private final OAuth2TokenCommonApi oauth2TokenApi;

    // ========== 门户用户存储常量（独立于后台，避免冲突） ==========
    /** 门户用户对象在Request中的存储Key */
    private static final String PORTAL_LOGIN_USER_ATTR = "PORTAL_LOGIN_USER";
    /** 门户用户ID在Request中的存储Key */
    private static final String PORTAL_LOGIN_USER_ID_ATTR = "PORTAL_LOGIN_USER_ID";

    @Override
    @SuppressWarnings("NullableProblems")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String token = SecurityFrameworkUtils.obtainAuthorization(request,
                securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
        if (StrUtil.isNotEmpty(token)) {
            // 1. 根据请求路径动态确定用户类型（门户/后台）
            Integer userType = determineUserTypeByRequest(request);

            try {
                // 2. 基于token构建登录用户
                LoginUser loginUser = buildLoginUserByToken(token, userType);
                // 3. 模拟登录（开发调试用，保留原有逻辑）
                if (loginUser == null) {
                    loginUser = mockLoginUser(request, token, userType);
                }

                // 4. 设置用户信息（核心修改：补充门户用户存入PORTAL_LOGIN_USER_ATTR）
                if (loginUser != null) {
                    // 4.1 保留原有：存入Spring Security上下文（供后台逻辑使用）
                    SecurityFrameworkUtils.setLoginUser(loginUser, request);

                    // 4.2 新增：若为门户用户，存入Request的PORTAL_LOGIN_USER_ATTR（与登录逻辑的常量一致）
                    if (UserTypeEnum.PORTAL_USER.getValue().equals(loginUser.getUserType())) {
                        request.setAttribute(PORTAL_LOGIN_USER_ATTR, loginUser);
                        request.setAttribute(PORTAL_LOGIN_USER_ID_ATTR, loginUser.getId());
                    }
                }
            } catch (Throwable ex) {
                // 异常处理：返回错误JSON（保留原有逻辑）
                CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
                ServletUtils.writeJSON(response, result);
                return;
            }
        }

        // 继续执行后续过滤器（保留原有逻辑）
        chain.doFilter(request, response);
    }
    /**
     * 根据请求路径判断用户类型（核心新增方法）
     */
    private Integer determineUserTypeByRequest(HttpServletRequest request) {
        String requestUri = request.getRequestURI();
        // 1. 门户请求：路径中包含 /portal/ 则视为门户用户
        if (requestUri.contains("/portal/")) {
            return UserTypeEnum.PORTAL_USER.getValue(); // 门户用户类型
        }
        // 2. 后台请求：默认视为管理员用户（路径中不含 /portal/）
        else {
            return UserTypeEnum.ADMIN.getValue(); // 后台管理员类型
        }
    }

    private LoginUser buildLoginUserByToken(String token, Integer userType) {
        try {
            OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
            if (accessToken == null) {
                return null;
            }

            // 用户类型不匹配校验（核心修改）
            if (userType != null) {
                // 1. 获取令牌中的实际用户类型
                Integer actualUserType = accessToken.getUserType();
                // 2. 校验是否匹配（支持后台、门户等合法类型）
                if (ObjectUtil.notEqual(actualUserType, userType)) {
                    // 增强错误信息：明确期望类型和实际类型，便于调试
                    throw new AccessDeniedException(
                            String.format("错误的用户类型：期望=%s，实际=%s",
                                    userType, actualUserType)
                    );
                }
            }

            // 构建登录用户（补充门户用户类型的兼容处理）
            return new LoginUser()
                    .setId(accessToken.getUserId())
                    .setUserType(accessToken.getUserType())
                    .setInfo(accessToken.getUserInfo())
                    .setTenantId(accessToken.getTenantId())
                    .setScopes(accessToken.getScopes())
                    .setExpiresTime(accessToken.getExpiresTime());
        } catch (ServiceException serviceException) {
            // 令牌校验失败（如过期、无效），返回null交由上层处理
            return null;
        }
    }

    /**
     * 模拟登录用户，方便日常开发调试
     *
     * 注意，在线上环境下，一定要关闭该功能！！！
     *
     * @param request 请求
     * @param token 模拟的 token，格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
     * @param userType 用户类型
     * @return 模拟的 LoginUser
     */
    private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
        if (!securityProperties.getMockEnable()) {
            return null;
        }
        // 必须以 mockSecret 开头
        if (!token.startsWith(securityProperties.getMockSecret())) {
            return null;
        }
        // 构建模拟用户
        Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
        return new LoginUser().setId(userId).setUserType(userType)
                .setTenantId(WebFrameworkUtils.getTenantId(request));
    }

}
